#include "AppStateManager.hpp"

#include <OgreWindowEventUtilities.h>

// Simple constructor that just initialises our one variable.
AppStateManager::AppStateManager()
{
	m_bShutdown = false;
}

// Destructor that empties both stacks.
AppStateManager::~AppStateManager()
{
	state_info si;

	// Empty out the ActiveStateStack.
	while (!m_ActiveStateStack.empty())
	{
		m_ActiveStateStack.back()->exit();
		m_ActiveStateStack.pop_back();
	}

	// Empty out the stack of state_infos. 
	while (!m_States.empty())
	{
		si = m_States.back();
		si.state->destroy();
		m_States.pop_back();
	}
}

// Called from within the state creation macro and sets some information of the
// new state, as well as pushing it on the active states stack.
void AppStateManager::manageAppState(Ogre::String stateName, AppState* state)
{
	try
	{
		state_info new_state_info;
		new_state_info.name = stateName;
		new_state_info.state = state;
		m_States.push_back(new_state_info);
	}
	catch (std::exception& e)
	{
		delete state;
		throw Ogre::Exception(Ogre::Exception::ERR_INTERNAL_ERROR, "Error while trying to manage a new AppState\n" + Ogre::String(e.what()), "AppStateManager.cpp (39)");
	}
}

// Returns a pointer to the state with the given name
AppState* AppStateManager::findByName(Ogre::String stateName)
{
	// Create an iterator and go through the vector linearly to find a match.
	std::vector<state_info>::iterator itr;
	for (itr=m_States.begin(); itr!=m_States.end(); itr++)
	{
		if (itr->name == stateName)
			return itr->state;
	}

	return 0;
}

// This is the really important part of the AppStateManager. It is the main loop
// of the application and follows these steps:
// 1. Change to the state specified.
// 2. Start loop.
// 3. Capture keyboard and mouse input.
// 4. Update the current state, represented by the top of the stack.
// 5. Call the OgreFramework class to update and render.
// 
// There is some time management stuff that needs to get done here. It is preferable
// to run the logic at a fixed framerate (or at least capped) in order to avoid 
// headaches. There's some new code added in here to do that. 
void AppStateManager::start(AppState* state)
{
	// 1. Change to the state specified.
	changeAppState(state);

	// This is going to be the hard limit for how often a frame can render.
	const int FPS_LIMIT = 60; // For some bizarre reason, 60 = 100 FPS
	double dTimeSinceLastFrame = 0.0;
	double dStartTime = 0.0;
	double dCurrentTime = 0.0f;

	// Get the start time in seconds by converting from milliseconds.
	dStartTime = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU();
	dCurrentTime = dStartTime;

	// 2. Start loop.
	// As long as there is no shutdown signal, keep looping the main event.
	while (!m_bShutdown)
	{
		// Check for waiting messages and handle them
		Ogre::WindowEventUtilities::messagePump();

		// Only run everything when the window is active. This creates a psuedo-pause
		// state when alt-tabbed out.
		if (OgreFramework::getSingletonPtr()->m_pRenderWnd->isActive())
		{
			// 3. Capture keyboard and mouse input.
			OgreFramework::getSingletonPtr()->m_pKeyboard->capture();
			OgreFramework::getSingletonPtr()->m_pMouse->capture();

			// 4. Update the current state, represented by the top of the stack. (Logic Update)
			// NOTE: I'm dividing by 1000 so as to get the difftime in seconds. It's much 
			// easier to think of difftime in seconds, and all the logic that is written is
			// under the assumption that difftime is in seconds.
			m_ActiveStateStack.back()->update(dTimeSinceLastFrame/1000.0);

			// 5. Call the OgreFramework class to update and render. (Graphics Update)
			OgreFramework::getSingletonPtr()->updateOgre(dTimeSinceLastFrame/1000.0);

			// 5. Render the frame as fast as possible.
			OgreFramework::getSingletonPtr()->m_pRoot->renderOneFrame();

			// Commented out the bad frame limiting code for now because it just makes less
			// and less sense as I think about it.

			// Hard limit the FPS.
			//int nTimeToWait = (1000.0f/FPS_LIMIT) - (dTimeSinceLastFrame);
			//if (nTimeToWait > 0)
			//	Sleep(nTimeToWait);
			

			// Now that Ogre has rendered its frame, set the new timeSinceLastFrame.
			dTimeSinceLastFrame = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU() - dCurrentTime;
			dCurrentTime = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU();
		}
		
		// When the program is alt tabbed, update slower, and also clear event times in order
		// to prevent a crash when the user tries to alt-tab back into it.
		else
		{
			OgreFramework::getSingletonPtr()->m_pRoot->clearEventTimes();
			#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
				Sleep(1000);
			#else
				sleep(1);
			#endif
		}
	}

	// The update cycle for the entire application is no longer running.
	OgreFramework::getSingletonPtr()->m_pLog->logMessage("Main loop quit");
}

// Exits the current state (if applicable) and starts the new specified one.
void AppStateManager::changeAppState(AppState* state)
{
	// If the stack isn't empty, then exit the state on the top of the 
	// current stack and pop it off.
	if (!m_ActiveStateStack.empty())
	{
		m_ActiveStateStack.back()->exit();
		m_ActiveStateStack.pop_back();
	}

	// Push the given state to the top of the stack. Then, initialise it
	// and then run the enter function of it to "enter" that state.
	m_ActiveStateStack.push_back(state);
	init(state);
	m_ActiveStateStack.back()->enter();
}

// Puts a new state on top of the stack and starts it.
bool AppStateManager::pushAppState(AppState* state)
{	
	// If the top of the stack is both empty and paused, ignore this command.
	if (!m_ActiveStateStack.empty())
	{
		if (!m_ActiveStateStack.back()->pause())
			return false;
	}

	// Push the given state to the top of the stack. Then, initialise it
	// and then run the enter function of it to "enter" that state.
	m_ActiveStateStack.push_back(state);
	init(state);
	m_ActiveStateStack.back()->enter();

	return true;
}

// Removes the top most state and resumes the one below it if there is one.
// If there isn't, then shut down the application because there aren't any
// states left to run. This is like running out of activation records. 
void AppStateManager::popAppState()
{
	// If the stack is not empty, then exit the state on the top of the stack 
	// and the pop it off the stack.
	if (!m_ActiveStateStack.empty())
	{
		m_ActiveStateStack.back()->exit();
		m_ActiveStateStack.pop_back();
	}

	// Start running the next state on the stack after we have popped off the 
	// previous top. HOWEVER, make sure that there is even a state left to run.
	if (!m_ActiveStateStack.empty())
	{
		init(m_ActiveStateStack.back());
		m_ActiveStateStack.back()->resume();
	}

	// If there was nothing left on the stack after we popped off the previous
	// element, then start shutting down the application.
	else
		shutdown();
}

// Exits all the existing app states on the stack and enters the given state.
void AppStateManager::popAllAndPushAppState(AppState* state)
{

	// This will exit and pop of all states on the stack.
	while (!m_ActiveStateStack.empty())
	{
		m_ActiveStateStack.back()->exit();
		m_ActiveStateStack.pop_back();
	}

	// Push the new given state.
	pushAppState(state);
}

// Pauses the current app state and resumes the one in the stack below.
void AppStateManager::pauseAppState()
{
	// As long as the stack isn't empty, pause the top of the stack.
	if (!m_ActiveStateStack.empty())
		m_ActiveStateStack.back()->pause();

	// Now, run the state that is one below the top of the stack. The second
	// topmost effectively. 
	if (m_ActiveStateStack.size() > 2)
	{
		init(m_ActiveStateStack.at(m_ActiveStateStack.size() - 2));
		m_ActiveStateStack.at(m_ActiveStateStack.size() -2)->resume();
	}
}

// Exit the application.
void AppStateManager::shutdown()
{
	m_bShutdown = true;
}

// Initialises a new state and links the input and SDKTrays callback on it, as 
// well as resetting the Ogre Statistics. (Triangle count, Batch count, etc.)
void AppStateManager::init(AppState* state)
{
	OgreFramework::getSingletonPtr()->m_pKeyboard->setEventCallback(state);
	OgreFramework::getSingletonPtr()->m_pMouse->setEventCallback(state);

	OgreFramework::getSingletonPtr()->m_pRenderWnd->resetStatistics();
}